package edu.uwm.RobberKnight.Model;

import java.awt.Color;
import java.awt.Point;
import java.util.*;

public class Board {
	public static int MAX_PLAYABLE_KNIGHTS = 5;
	private int _boundary; 
	private int _playableKnights;
	private int _playableTiles;
	private Tile[][] _tiles;
	private RobberKnightsGame _game;
	private Tile _lastPlacedTile;
	private int _leftBoundary;
	private int _rightBoundary;
	private int _topBoundary;
	private int _botomBoundary;
	private boolean _haveDirection;
	private String _direction;//_direction can be left, right, up or down. So maybe it's better to define it as an enum.
	private List<Point> _lstUsedTiles;
	
	public Board(RobberKnightsGame game) {
		_haveDirection = false;
		_direction = "";
		_lstUsedTiles = new LinkedList<Point>();
		_game = game;
		setBoundry();
		nextTurn();
	}
	
	public void setBoundry(){
		int playerSize = _game.getPlayers().size();
		switch (playerSize) {
		case 2:
			_boundary = 7;
			_tiles = new Tile[12][12];			
			break;
		case 3:
			_boundary = 9;
			_tiles = new Tile[15][16];
			break;
		case 4:
			_boundary = 10;
			_tiles = new Tile[16][18];
			break;
		}
		_leftBoundary = _boundary - playerSize;
		_rightBoundary = _boundary - 1;
		_topBoundary = _boundary - 2;
		_botomBoundary = _boundary -1;
	}
	
	/*
	 * Returns how many knights may yet be played this turn.
	 */
	public int getPlayableKnights() {
		return _playableKnights;
	}
	
	/*
	 * Return the tiles that the player can legally place knights on.
	 */
	public LinkedList<Tile> getValidMoves(){

		LinkedList<Tile> retval = new LinkedList<Tile>();
		if(!isKnightsPlayable())
			return retval;
		
		if(_lastPlacedTile.getKnights().size() < _lastPlacedTile.getMinKnight()){
			retval.add(_tiles[_lastPlacedTile.xPosition()][_lastPlacedTile.yPosition()]);
		} else if(!_haveDirection){
			int x = _lastPlacedTile.xPosition();
			int y = _lastPlacedTile.yPosition();
			
			if(_tiles[x][y].getKnights().size() < MAX_PLAYABLE_KNIGHTS){
				retval.add(_tiles[x][y]);
			}

			if(x <= _tiles.length - 2)
				if(_tiles[x + 1][y] != null){
					if(checkValidKnightNumbers(x + 1, y)){
						retval.add(_tiles[x + 1][y]);
					}
				}
				
			if(x >= 1)
				if(_tiles[x - 1][y] != null){
					if(checkValidKnightNumbers(x - 1, y)){
						retval.add(_tiles[x - 1][y]);
					}
				}
				
			if(y <= _tiles[0].length - 2)
				if(_tiles[x][y + 1] != null){
					if(checkValidKnightNumbers(x, y + 1)){
						retval.add(_tiles[x][y + 1]);
					}
				}
			
			if(y >= 1)
				if(_tiles[x][y - 1] != null){
					if(checkValidKnightNumbers(x, y - 1)){
						retval.add(_tiles[x][y - 1]);
					}
				}
		} else{
			for (Point point : _lstUsedTiles) {
				if(checkValidKnightNumbers(point.x, point.y)){
					retval.add(_tiles[point.x][point.y]);
				}
			}
			
			if(_direction.equals("left")){
				int minX = _lastPlacedTile.xPosition();
				for (Point point : _lstUsedTiles) {
					if(point.x < minX){
						minX = point.x;
					}
				}
				if(minX >= 1)
					if(_tiles[minX - 1][_lastPlacedTile.yPosition()] != null)
						if(checkValidKnightNumbers(minX - 1, _lastPlacedTile.yPosition())){
							retval.add(_tiles[minX - 1][_lastPlacedTile.yPosition()]);
						}
			}
			
			if(_direction.equals("right")){
				int maxX = _lastPlacedTile.xPosition();
				for (Point point : _lstUsedTiles) {
					if(point.x > maxX){
						maxX = point.x;
					}
				}
				if(maxX <= _tiles.length - 2)
					if(_tiles[maxX + 1][_lastPlacedTile.yPosition()] != null)
						if(checkValidKnightNumbers(maxX + 1, _lastPlacedTile.yPosition())){
							retval.add(_tiles[maxX + 1][_lastPlacedTile.yPosition()]);
						}
			}
			
			if(_direction.equals("up")){
				int minY = _lastPlacedTile.yPosition();
				for (Point point : _lstUsedTiles) {
					if(point.y < minY){
						minY = point.y;
					}
				}
				if(minY >= 1)
					if(_tiles[_lastPlacedTile.xPosition()][minY - 1] != null)
						if(checkValidKnightNumbers(_lastPlacedTile.xPosition(), minY - 1)){
							retval.add(_tiles[_lastPlacedTile.xPosition()][minY - 1]);
						}
			}
			
			if(_direction.equals("down")){
				int maxY = _lastPlacedTile.yPosition();
				for (Point point : _lstUsedTiles) {
					if(point.y > maxY){
						maxY = point.y;
					}
				}
				if(maxY <= _tiles[0].length - 2)
					if(_tiles[_lastPlacedTile.xPosition()][maxY + 1] != null)
						if(checkValidKnightNumbers(_lastPlacedTile.xPosition(), maxY + 1)){
							retval.add(_tiles[_lastPlacedTile.xPosition()][maxY + 1]);
						}
			}
		}
		
		return retval;
	}
	
	private boolean checkValidKnightNumbers(int x, int y){
		if(_tiles[x][y].getTileType() == TileType.LAKE){
			return false;
		}else if(_tiles[x][y].getTileType() == TileType.PLAIN || _tiles[x][y].getTileType() == TileType.CASTLE_PLAIN || _tiles[x][y].getTileType() == TileType.TOWN_PLAIN || _tiles[x][y].getTileType() == TileType.VILLAGE_PLAIN){
			if(_tiles[x][y].getKnights().size() <= MAX_PLAYABLE_KNIGHTS - 1 && _playableKnights >= 1){
				return true;
			}
		}else if(_tiles[x][y].getTileType() == TileType.CASTLE_FOREST || _tiles[x][y].getTileType() == TileType.TOWN_FOREST || _tiles[x][y].getTileType() == TileType.VILLAGE_FOREST){
			if(_tiles[x][y].getKnights().size() <= MAX_PLAYABLE_KNIGHTS - 2 && _playableKnights >= 2){
				return true;
			}
		} else if(_tiles[x][y].getTileType() == TileType.MOUNTAIN){
			if(_tiles[x][y].getKnights().size() <= MAX_PLAYABLE_KNIGHTS - 3 && _playableKnights >= 3){
				return true;
			}
		}
		return false;
	}
	
	/*
	 * getValidLocs	Returns a collection of coordinate pairs that map to places where tiles may legally be placed.
	 */
	public LinkedList<Point> getValidLocs(){
		
		LinkedList<Point> retval = new LinkedList<Point>();
		if(_game.getState() == GameState.initialTilePlacement){
			retval = getValidInitialLocs();
		} else{
			for(int x = 0; x < _tiles.length; x++) {
				for(int y = 0; y < _tiles[x].length; y++) {
					if(_tiles[x][y] == null){
						if(x + 1 < _tiles.length){//check left empty slot
							if(_tiles[x + 1][y] != null && _rightBoundary - x < _boundary){
								retval.add(new Point(x, y));
								continue;
							}
						}
						if(x - 1 >= 0){//check right empty slot
							if(_tiles[x - 1][y] != null && x - _leftBoundary < _boundary){
								retval.add(new Point(x, y));
								continue;
							}
						}
						if(y + 1 < _tiles[x].length){//check top empty slot
							if(_tiles[x][y + 1] != null && _botomBoundary -y < _boundary){
								retval.add(new Point(x, y));
								continue;
							}
						}
						if(y - 1 >= 0){//check bottom empty slot
							if(_tiles[x][y - 1] != null && y - _topBoundary < _boundary){
								retval.add(new Point(x, y));
							}
						}
					}
				}
			}
		}
		return retval;
	}
	
	/**
	 * Returns the tile locations that are initially available in the setup state
	 */
	private LinkedList<Point> getValidInitialLocs() {
		LinkedList<Point> retval = new LinkedList<Point>();
		for(int x = _boundary - _game.getPlayers().size(); x < _boundary; x++) {
			for(int y = _boundary - 2; y < _boundary; y++) {
				if(_tiles[x][y] == null){
					retval.add(new Point(x,y));
				}
			}
		}
		return retval;
	}
	
	/*
	 * placeKnights	Request to place the specified knights onto the specified tile. Returns whether the operation was a success.
	 */
	public boolean placeKnights(Tile tile, Player p){
		if(!getValidMoves().contains(tile) || p != _game.getCurrentPlayer()) {
			return false;
		}
		if(tile.getMinKnight() > p.getKnights().size() || _playableKnights <= 0) {
			_lastPlacedTile = null;
			return false;
		}
		if(tile.isCastle()){
			if(!tile.getKnights().isEmpty()){
				if((tile.getMinKnight() + tile.getKnights().size()) > Tile.MAX_KNIGHTS) {
					return false;
				}
			}
		} else{
			if((tile.getMinKnight() + tile.getKnights().size()) > Tile.MAX_KNIGHTS) {
				return false;
			}
		}
		for(int i=0; i < tile.getMinKnight(); i++){
			tile.addKnights(p.removeKnight());
		}
		if(_lastPlacedTile.getPosition().x != tile.xPosition() || _lastPlacedTile.getPosition().y != tile.yPosition()){
			_haveDirection = true;
		}
		Point point = new Point(tile.xPosition(), tile.yPosition());
		if(!_lstUsedTiles.contains(point)){
			_lstUsedTiles.add(point);
		}
		
		if(tile.getPosition().x < _lastPlacedTile.xPosition()){
			_direction = "left";
		} else if(tile.getPosition().x > _lastPlacedTile.xPosition()){
			_direction = "right";
		} else if (tile.getPosition().y < _lastPlacedTile.yPosition()){
			_direction = "up";
		} else if(tile.getPosition().y > _lastPlacedTile.yPosition()){
			_direction = "down";
		}
		
		_playableKnights -= tile.getMinKnight();
		System.out.println("Payable Knights : " + _playableKnights);

		_game.print(tile.getPlayer().getName() + " placed " + tile.getMinKnight() + " knight(s) at (" + tile.getPosition().x + ", " + tile.getPosition().y + ")");
		this.calcScore();
		return true;
	}
	
	/*
	 * Places a tile at the requested location. Returns whether the tile was successfully placed.
	 */
	public boolean placeTile(Tile tile, int x, int y) {
		if(_playableTiles > 0 && isValidTilePlacement(x, y) && tile.getPlayer() == _game.getCurrentPlayer()) {
			_haveDirection = false;
			_lstUsedTiles.clear();
			_direction = "";
			_tiles[x][y] = tile;
			tile.setPosition(x, y);
			tile.getPlayer().placeTile(tile);
			_game.print(tile.getPlayer().getName() + " placed tile at (" + x + ", " + y + ")");
			if(_game.getState() != GameState.initialTilePlacement )
				_lastPlacedTile = tile;
			if(_game.getState() == GameState.regularPlay && (tile.getTileType()== TileType.CASTLE_FOREST || tile.getTileType() == TileType.CASTLE_PLAIN)) {
				_playableKnights = MAX_PLAYABLE_KNIGHTS;
			} else {
				_playableKnights =0;
			}
			//If we are in initial tile placement phase and there are no valid locations, proceed to regular game play
			if(_game.getState() == GameState.initialTilePlacement && getValidLocs().size() == 0) {
				_game.proceedToRegularPlay();
			}
			--_playableTiles;
			System.out.println("Payable tiles : " + _playableTiles);
			updateBoundaries(x,y);
			return true;
		}
		return false;
	}
	
	/**
	 * Keeps track of outer board boundaries
	 * @param x
	 * @param y
	 */
	private void updateBoundaries(int x, int y){
		if(x < _leftBoundary){
			_leftBoundary = x;
		}
		if(x > _rightBoundary){
			_rightBoundary = x;
		}
		if(y < _topBoundary){
			_topBoundary = y;
		}
		if(y > _botomBoundary){
			_botomBoundary = y;
		}
	}
	
	/**
	 * Returns whether or not the location is valid for a tile to be placed there
	 * @param x
	 * @param y
	 * @return
	 */
	public boolean isValidTilePlacement(int x, int y) {
		for(Point p : getValidLocs())
			if(p.x == x && p.y == y)
				return true;
		return false;
	}
	
	/**
	 * Returns a collection of tiles currently on the board
	 */
	public LinkedList<Tile> getTiles() {
		LinkedList<Tile> retval = new LinkedList<Tile>();
		for(int x = 0; x < _tiles.length; x++) {
			for(int y = 0; y < _tiles[x].length; y++) {
				if(_tiles[x][y] != null)
					retval.add(_tiles[x][y]);
			}
		}
		return retval;
	}
	
	/**
	 * Resets the number of playable tiles available to the player
	 */
	public void nextTurn() {
		if(_game.getState() == GameState.initialTilePlacement) {
			_playableTiles = 1;
		} else {
			_playableTiles = 3;
		}
		_lastPlacedTile = null;
	}
	
	/**
	 * Returns the width of a tile
	 */
	public int getWidth() {
		return _tiles.length;
	}
	
	/**
	 * Returns the height of a tile
	 */
	public int getHeight() {
		return _tiles[0].length;
	}
	
	/**
	 * Returns the number of playable left for the current move
	 */
	public int getPlayableTiles() {
		return _playableTiles;
	}

	/**
	 * Returns whether or not the current player may place knights
	 */
	public boolean isKnightsPlayable() {
		if(_lastPlacedTile == null || _game.getState()==GameState.initialTilePlacement || _playableKnights <= 0)
			return false;
		if(_lastPlacedTile.isCastle())
			return true;
		return false;
			
	}
	
	public void calcScore() {
		int p1 = 0;
		int p2 = 0;
		int p3 = 0;
		int p4 = 0;
		for(int i=0;i<_tiles.length;i++) {
			for(int j=0;j<_tiles[i].length;j++) {
				if(_tiles[i][j] != null && _tiles[i][j].getKnights() != null && _tiles[i][j].getKnights().size() > 0) {
					if(_tiles[i][j].getKnights().peek().getPlayer().getColor() == Color.red) {
						p1 += _tiles[i][j].getScore();
					}
					else if(_tiles[i][j].getKnights().peek().getPlayer().getColor() == Color.blue) {
						p2 += _tiles[i][j].getScore();
					}
					else if(_tiles[i][j].getKnights().peek().getPlayer().getColor() == Color.green) {
						p3 += _tiles[i][j].getScore();
					}
					else if(_tiles[i][j].getKnights().peek().getPlayer().getColor() == Color.yellow) {
						p4 += _tiles[i][j].getScore();
					}
				}
			}
		}
		for(Player player : _game.getPlayers()) {
			if(player.getColor() == Color.red)
				player.setScore(p1);
			if(player.getColor() == Color.blue)
				player.setScore(p2);
			if(player.getColor() == Color.green)
				player.setScore(p3);
			if(player.getColor() == Color.yellow)
				player.setScore(p4);
		}
	}
}
